package com.example.iso9797demo;

import org.bouncycastle.crypto.Mac;
import org.bouncycastle.crypto.engines.DESEngine;
import org.bouncycastle.crypto.macs.ISO9797Alg3Mac;
import org.bouncycastle.crypto.paddings.BlockCipherPadding;
import org.bouncycastle.crypto.paddings.ISO10126d2Padding;
import org.bouncycastle.crypto.paddings.ISO7816d4Padding;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.TBCPadding;
import org.bouncycastle.crypto.paddings.X923Padding;
import org.bouncycastle.crypto.paddings.ZeroBytePadding;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import android.util.Log;

import java.util.Date;
import java.util.logging.Logger;

/**
 * ISO9797 算法 3 MAC 计算助手类
 * 
 * 实现功能：
 * 1. 支持双长度密钥（16字节）和三长度密钥（24字节）
 * 2. 支持 ISO7816-4 填充模式
 * 3. 支持零填充模式
 * 4. 基于 Bouncy Castle 库的 ISO9797Alg3Mac 实现
 * 
 * 算法说明：
 * - 算法标准: ISO 9797-1 Algorithm 3 / ANSI X9.19 Retail MAC
 * - 加密引擎: DES (Data Encryption Standard)
 * - 工作模式: CBC (Cipher Block Chaining)
 * - 核心结构: Encrypt-Decrypt-Encrypt (EDE)
 * 
 * 使用示例：
 * <pre>
 * ISO9797MacHelper helper = new ISO9797MacHelper();
 * String mac = helper.calculateMAC("Hello", "7CA110454A1A6E570131D9619DC1376E", true);
 * </pre>
 */
public class ISO9797MacHelper {
    private static final Logger LOGGER = Logger.getLogger(ISO9797MacHelper.class.getName());
    
    /**
     * 计算 ISO9797 算法 3 MAC 值
     * 
     * @param inputData 输入数据（字符串）
     * @param keyHex 密钥（十六进制字符串，32或48字符）
     * @param useISO7816Padding 是否使用 ISO7816-4 填充
     * @return MAC 值（十六进制字符串，16个字符 = 8字节）
     * @throws Exception 当密钥格式错误或计算失败时抛出异常
     */
    public String calculateMAC(String inputData, String keyHex, boolean useISO7816Padding) 
            throws Exception {
        return calculateMAC(inputData, keyHex, null, useISO7816Padding);
    }
    
    /**
     * 计算 ISO9797 算法 3 MAC 值（支持 IV）
     * 
     * @param inputData 输入数据（字符串）
     * @param keyHex 密钥（十六进制字符串，32或48字符）
     * @param ivHex IV初始化向量（十六进制字符串，16字符=8字节，可为null）
     * @param useISO7816Padding 是否使用 ISO7816-4 填充
     * @return MAC 值（十六进制字符串，16个字符 = 8字节）
     * @throws Exception 当密钥格式错误或计算失败时抛出异常
     */
    public String calculateMAC(String inputData, String keyHex, String ivHex, boolean useISO7816Padding) 
            throws Exception {
        byte[] dataBytes = inputData.getBytes("UTF-8");
        return calculateMACFromBytes(dataBytes, keyHex, ivHex, useISO7816Padding);
    }
    
    /**
     * 从字节数组计算 ISO9797 算法 3 MAC 值（支持 IV）
     * 
     * @param dataBytes 输入数据（字节数组）
     * @param keyHex 密钥（十六进制字符串，32或48字符）
     * @param ivHex IV初始化向量（十六进制字符串，16字符=8字节，可为null）
     * @param useISO7816Padding 是否使用 ISO7816-4 填充
     * @return MAC 值（十六进制字符串，16个字符 = 8字节）
     * @throws Exception 当密钥格式错误或计算失败时抛出异常
     */
    public String calculateMACFromBytes(byte[] dataBytes, String keyHex, String ivHex, boolean useISO7816Padding) 
            throws Exception {
        // 步骤 1: 将十六进制密钥转换为字节数组
        byte[] keyBytes = hexStringToBytes(keyHex);
        
        // 验证密钥长度
        if (keyBytes.length != 16 && keyBytes.length != 24) {
            throw new IllegalArgumentException(
                "密钥长度必须是 16 字节（双长度）或 24 字节（三长度），当前: " + keyBytes.length + " 字节"
            );
        }
        
        // 步骤 2: 处理 IV（如果提供）
        byte[] ivBytes = null;
        if (ivHex != null && !ivHex.isEmpty()) {
            ivBytes = hexStringToBytes(ivHex);
            if (ivBytes.length != 8) {
                throw new IllegalArgumentException(
                    "IV 长度必须是 8 字节，当前: " + ivBytes.length + " 字节"
                );
            }
        }
        
        // 步骤 3: 创建 ISO9797Alg3Mac 实例
        Mac mac;
        if (useISO7816Padding) {
            // 使用 ISO7816-4 填充
            // 填充格式: 第一个填充字节为 0x80，后续填充字节为 0x00
            mac = new ISO9797Alg3Mac(new DESEngine(), new ISO7816d4Padding());
        } else {
            // 使用零填充（默认）
            // 填充格式: 所有填充字节为 0x00
            mac = new ISO9797Alg3Mac(new DESEngine());
        }
        
        // 步骤 4: 初始化 MAC（设置密钥和 IV）
        // 密钥会被自动分解为 K1, K2, K3
        // - 16 字节密钥: K1=前8字节, K2=后8字节, K3=K1
        // - 24 字节密钥: K1=前8字节, K2=中8字节, K3=后8字节
        if (ivBytes != null) {
            // 使用 IV 初始化
            mac.init(new ParametersWithIV(new KeyParameter(keyBytes), ivBytes));
        } else {
            // 不使用 IV
            mac.init(new KeyParameter(keyBytes));
        }
        long timeS = new Date().getTime();
        LOGGER.info("ISO9797Alg3Mac:: update start time : " + timeS + "  输入数据字节数为：" + dataBytes.length);
        // 步骤 5: 更新数据
        // 数据会被分块处理，每块 8 字节（DES 块大小）
        mac.update(dataBytes, 0, dataBytes.length);
        long timeE = new Date().getTime();
        LOGGER.info("ISO9797Alg3Mac:: update end time : " + timeE);
        long num = timeE - timeS;
        LOGGER.info("ISO9797Alg3Mac:: 耗时为 : " + num);
        
        // 步骤 6: 计算最终 MAC 值
        // 核心算法:
        // 1. 使用 K1 对所有数据进行 CBC-DES 加密
        // 2. 对最后一块使用 K2 进行 DES 解密
        // 3. 对解密结果使用 K3 进行 DES 加密
        // 4. 输出最终 MAC 值（8 字节）
        byte[] macValue = new byte[mac.getMacSize()];
        mac.doFinal(macValue, 0);
        
        // 步骤 7: 转换为十六进制字符串返回
        return bytesToHexString(macValue);
    }
    
    /**
     * 从字节数组计算 ISO9797 算法 3 MAC 值（支持 IV 和指定填充模式）
     * 
     * @param dataBytes 输入数据（字节数组）
     * @param keyHex 密钥（十六进制字符串，32或48字符）
     * @param ivHex IV初始化向量（十六进制字符串，16字符=8字节，可为null）
     * @param paddingMode 填充模式名称
     * @return MAC 值（十六进制字符串，16个字符 = 8字节）
     * @throws Exception 当密钥格式错误或计算失败时抛出异常
     */
    public String calculateMACFromBytesWithPadding(byte[] dataBytes, String keyHex, String ivHex, String paddingMode) 
            throws Exception {
        // 步骤 1: 将十六进制密钥转换为字节数组
        byte[] keyBytes = hexStringToBytes(keyHex);
        
        // 验证密钥长度
        if (keyBytes.length != 16 && keyBytes.length != 24) {
            throw new IllegalArgumentException(
                "密钥长度必须是 16 字节（双长度）或 24 字节（三长度），当前: " + keyBytes.length + " 字节"
            );
        }
        
        // 步骤 2: 处理 IV（如果提供）
        byte[] ivBytes = null;
        if (ivHex != null && !ivHex.isEmpty()) {
            ivBytes = hexStringToBytes(ivHex);
            if (ivBytes.length != 8) {
                throw new IllegalArgumentException(
                    "IV 长度必须是 8 字节，当前: " + ivBytes.length + " 字节"
                );
            }
        }
        
        // 步骤 3: 根据填充模式创建 BlockCipherPadding 实例
        BlockCipherPadding padding = createPadding(paddingMode);
        
        // 步骤 4: 创建 ISO9797Alg3Mac 实例
        Mac mac = new ISO9797Alg3Mac(new DESEngine(), padding);
        
        // 步骤 5: 初始化 MAC（设置密钥和 IV）
        // 密钥会被自动分解为 K1, K2, K3
        // - 16 字节密钥: K1=前8字节, K2=后8字节, K3=K1
        // - 24 字节密钥: K1=前8字节, K2=中8字节, K3=后8字节
        if (ivBytes != null) {
            // 使用 IV 初始化
            mac.init(new ParametersWithIV(new KeyParameter(keyBytes), ivBytes));
        } else {
            // 不使用 IV
            mac.init(new KeyParameter(keyBytes));
        }
        
        // 步骤 6: 更新数据
        // 数据会被分块处理，每块 8 字节（DES 块大小）
        mac.update(dataBytes, 0, dataBytes.length);
        
        // 步骤 7: 计算最终 MAC 值
        byte[] macValue = new byte[mac.getMacSize()];
        mac.doFinal(macValue, 0);
        
        // 步骤 8: 转换为十六进制字符串返回
        return bytesToHexString(macValue);
    }
    
    /**
     * 根据填充模式名称创建对应的 BlockCipherPadding 实例
     * 
     * @param paddingMode 填充模式名称
     * @return BlockCipherPadding 实例
     */
    public BlockCipherPadding createPadding(String paddingMode) {
        switch (paddingMode) {
            case "ZeroBytePadding":
                ZeroBytePadding zeroPadding = new ZeroBytePadding();
                zeroPadding.init(null);
                return zeroPadding;
            case "ISO7816d4Padding":
                ISO7816d4Padding iso7816Padding = new ISO7816d4Padding();
                iso7816Padding.init(null);
                return iso7816Padding;
            case "PKCS7Padding":
                PKCS7Padding pkcs7Padding = new PKCS7Padding();
                pkcs7Padding.init(null);
                return pkcs7Padding;
            case "ISO10126d2Padding":
                ISO10126d2Padding iso10126Padding = new ISO10126d2Padding();
                iso10126Padding.init(new java.security.SecureRandom());
                return iso10126Padding;
            case "X923Padding":
                X923Padding x923Padding = new X923Padding();
                x923Padding.init(null);
                return x923Padding;
            case "TBCPadding":
                TBCPadding tbcPadding = new TBCPadding();
                tbcPadding.init(null);
                return tbcPadding;
            default:
                // 默认使用零填充
                ZeroBytePadding defaultPadding = new ZeroBytePadding();
                defaultPadding.init(null);
                return defaultPadding;
        }
    }


    /**
     * 十六进制字符串转字节数组
     * 
     * 示例:
     * "7CA1" -> [0x7C, 0xA1]
     * 
     * @param hex 十六进制字符串（不区分大小写，可包含空格）
     * @return 字节数组
     */
    public byte[] hexStringToBytes(String hex) {
        // 移除所有空格
        hex = hex.replaceAll("\\s+", "");
        
        // 确保长度为偶数
        if (hex.length() % 2 != 0) {
            throw new IllegalArgumentException("十六进制字符串长度必须是偶数");
        }
        
        int len = hex.length();
        byte[] data = new byte[len / 2];
        
        for (int i = 0; i < len; i += 2) {
            // 每两个十六进制字符转换为一个字节
            int high = Character.digit(hex.charAt(i), 16);
            int low = Character.digit(hex.charAt(i + 1), 16);
            
            if (high == -1 || low == -1) {
                throw new IllegalArgumentException(
                    "无效的十六进制字符: " + hex.substring(i, i + 2)
                );
            }
            
            data[i / 2] = (byte) ((high << 4) | low);
        }
        
        return data;
    }
    
    /**
     * 字节数组转十六进制字符串
     * 
     * 示例:
     * [0x7C, 0xA1] -> "7CA1"
     * 
     * @param bytes 字节数组
     * @return 十六进制字符串（大写）
     */
    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            // 每个字节转换为两个十六进制字符
            sb.append(String.format("%02X", b & 0xFF));
        }
        return sb.toString();
    }
}
